/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// light.c

#include "light.h"
#include "lightcommon.h"
#ifdef _WIN32
#ifdef _TTIMOBUILD
#include "pakstuff.h"
#else
#include "../libs/pakstuff.h"
#endif
#endif

#include "progress.h"
#include "radiosity.h"

#define LIGHTMAP_BYTESIZE	(LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 3)

#define	EXTRASCALE	2

typedef struct {
	float		plane[4];
	vec3_t		origin;
	vec3_t		vectors[2];
	shaderInfo_t	*si;
} filter_t;

#define	MAX_FILTERS	1024
filter_t	filters[MAX_FILTERS];
int			numFilters;

extern char	source[1024];

int			numLightTexels;

qboolean	notrace;
qboolean	patchshadows;
qboolean	dump;
qboolean	extra;
qboolean	extraWide;
qboolean	lightmapBorder;

qboolean	noSurfaces;

int			samplesize = 16;		//sample size in units
int			novertexlighting = 0;
int			nogridlighting = 0;
int			nogridradiosity = 0;
int			nortlights = 0;

// for run time tweaking of all area sources in the level
float		areaScale =	0.25;

// for run time tweaking of all point sources in the level
float		pointScale = 7500;

qboolean	exactPointToPolygon = qfalse;

float		formFactorValueScale = 3;

float		linearScale = 1.0 / 8000;

light_t		*lights;
light_t		fakeSunLight;
int			numPointLights;
int			numAreaLights;

FILE		*dumpFile;

int			c_visible, c_occluded;

//int			defaultLightSubdivide = 128;		// vary by surface size?
//int			defaultLightSubdivide = 999;		// vary by surface size?
int				defaultLightSubdivide = 64;

vec3_t		ambientColor;

vec3_t		surfaceOrigin[ MAX_MAP_DRAW_SURFS ];
int			entitySurface[ MAX_MAP_DRAW_SURFS ];

// 7,9,11 normalized to avoid being nearly coplanar with common faces
//vec3_t		sunDirection = { 0.441835, 0.56807, 0.694313 };
//vec3_t		sunDirection = { 0.45, 0, 0.9 };
//vec3_t		sunDirection = { 0, 0, 1 };

// these are usually overrided by shader values
vec3_t		sunDirection = { 0.45, 0.3, 0.9 };
vec3_t		sunLight = { 100, 100, 50 };



typedef struct {
	dbrush_t	*b;
	vec3_t		bounds[2];
} skyBrush_t;

int			numSkyBrushes;
skyBrush_t	skyBrushes[MAX_MAP_BRUSHES];


/*

the corners of a patch mesh will always be exactly at lightmap samples.
The dimensions of the lightmap will be equal to the average length of the control
mesh in each dimension divided by 2.
The lightmap sample points should correspond to the chosen subdivision points.

*/

/*
===============================================================

SURFACE LOADING

===============================================================
*/

#define	MAX_FACE_POINTS		128

/*
===============
SubdivideAreaLight

Subdivide area lights that are very large
A light that is subdivided will never backsplash, avoiding weird pools of light near edges
===============
*/
void SubdivideAreaLight( shaderInfo_t *ls, winding_t *w, vec3_t normal, 
						float areaSubdivide, qboolean backsplash ) {
	float			area, value, intensity;
	light_t			*dl, *dl2;
	vec3_t			mins, maxs;
	int				axis;
	winding_t		*front, *back;
	vec3_t			planeNormal;
	float			planeDist;

	if ( !w ) {
		return;
	}

	WindingBounds( w, mins, maxs );

	// check for subdivision
	for ( axis = 0 ; axis < 3 ; axis++ ) {
		if ( maxs[axis] - mins[axis] > areaSubdivide ) {
			VectorClear( planeNormal );
			planeNormal[axis] = 1;
			planeDist = ( maxs[axis] + mins[axis] ) * 0.5;
			ClipWindingEpsilon ( w, planeNormal, planeDist, ON_EPSILON, &front, &back );
			SubdivideAreaLight( ls, front, normal, areaSubdivide, qfalse );
			SubdivideAreaLight( ls, back, normal, areaSubdivide, qfalse );
			FreeWinding( w );
			return;
		}
	}

	// create a light from this
	area = WindingArea (w);
	if ( area <= 0 || area > 20000000 ) {
		return;
	}

	numAreaLights++;
	dl = malloc(sizeof(*dl));
	memset (dl, 0, sizeof(*dl));
	dl->next = lights;
	lights = dl;
	dl->type = emit_area;

	WindingCenter( w, dl->origin );
	dl->w = w;
	VectorCopy ( normal, dl->normal);
	dl->dist = DotProduct( dl->origin, normal );

	value = ls->value;
	intensity = value * area * areaScale;
	VectorAdd( dl->origin, dl->normal, dl->origin );

	VectorCopy( ls->color, dl->color );

	dl->photons = intensity;

	// emitColor is irrespective of the area
	VectorScale( ls->color, value*formFactorValueScale*areaScale, dl->emitColor );

	dl->si = ls;

	if ( ls->contents & CONTENTS_FOG ) {
		dl->twosided = qtrue;
	}

	// optionally create a point backsplash light
	if ( backsplash && ls->backsplashFraction > 0 ) {
		dl2 = malloc(sizeof(*dl));
		memset (dl2, 0, sizeof(*dl2));
		dl2->next = lights;
		lights = dl2;
		dl2->type = emit_point;

		VectorMA( dl->origin, ls->backsplashDistance, normal, dl2->origin );

		VectorCopy( ls->color, dl2->color );

		dl2->photons = dl->photons * ls->backsplashFraction;
		dl2->si = ls;
	}
}


/*
===============
CreateSurfaceLights

This creates area lights
===============
*/
void CreateSurfaceLights( void ) {
	int				i, j, side;
	dsurface_t		*ds;
	shaderInfo_t	*ls;
	winding_t		*w;
	cFacet_t		*f;
	light_t			*dl;
	vec3_t			origin;
	drawVert_t		*dv;
	int				c_lightSurfaces;
	float			lightSubdivide;
	vec3_t			normal;

	qprintf ("--- CreateSurfaceLights ---\n");
	c_lightSurfaces = 0;

	for ( i = 0 ; i < numDrawSurfaces ; i++ ) {
		// see if this surface is light emiting
		ds = &drawSurfaces[i];

		ls = ShaderInfoForShader( dshaders[ ds->shaderNum].shader );
		if ( ls->value == 1.0f || ls->value == 0.0f ) {
			continue;
		}

		// determine how much we need to chop up the surface
		if ( ls->lightSubdivide ) {
			lightSubdivide = ls->lightSubdivide;
		} else {
			lightSubdivide = defaultLightSubdivide;
		}

		c_lightSurfaces++;

		// an autosprite shader will become
		// a point light instead of an area light
		if ( ls->autosprite ) {
			// autosprite geometry should only have four vertexes
			if ( surfaceTest[i] ) {
				// curve or misc_model
				f = surfaceTest[i]->facets;
				if ( surfaceTest[i]->numFacets != 1 || f->numBoundaries != 4 ) {
					_printf( "WARNING: surface at (%i %i %i) has autosprite shader but isn't a quad\n",
						(int)f->points[0], (int)f->points[1], (int)f->points[2] );
				}
				VectorAdd( f->points[0], f->points[1], origin );
				VectorAdd( f->points[2], origin, origin );
				VectorAdd( f->points[3], origin, origin );
				VectorScale( origin, 0.25, origin );
			} else {
				// normal polygon
				dv = &drawVerts[ ds->firstVert ];
				if ( ds->numVerts != 4 ) {
					_printf( "WARNING: surface at (%i %i %i) has autosprite shader but %i verts\n",
						(int)dv->xyz[0], (int)dv->xyz[1], (int)dv->xyz[2] );
					continue;
				}

				VectorAdd( dv[0].xyz, dv[1].xyz, origin );
				VectorAdd( dv[2].xyz, origin, origin );
				VectorAdd( dv[3].xyz, origin, origin );
				VectorScale( origin, 0.25, origin );
			}


			numPointLights++;
			dl = malloc(sizeof(*dl));
			memset (dl, 0, sizeof(*dl));
			dl->next = lights;
			lights = dl;

			VectorCopy( origin, dl->origin );
			VectorCopy( ls->color, dl->color );
			dl->photons = ls->value * pointScale;
			dl->type = emit_point;
			continue;
		}

		// possibly create for both sides of the polygon
		for ( side = 0 ; side <= ls->twoSided ; side++ ) {
			// create area lights
			if ( surfaceTest[i] ) {
				// curve or misc_model
				for ( j = 0 ; j < surfaceTest[i]->numFacets ; j++ ) {
					f = surfaceTest[i]->facets + j;
					w = AllocWinding( f->numBoundaries );
					w->numpoints = f->numBoundaries;
					memcpy( w->p, f->points, f->numBoundaries * 12 );

					VectorCopy( f->surface, normal );
					if ( side ) {
						winding_t	*t;

						t = w;
						w = ReverseWinding( t );
						FreeWinding( t );
						VectorSubtract( vec3_origin, normal, normal );
					}
					SubdivideAreaLight( ls, w, normal, lightSubdivide, qtrue );
				}
			} else {
				// normal polygon

				w = AllocWinding( ds->numVerts );
				w->numpoints = ds->numVerts;
				for ( j = 0 ; j < ds->numVerts ; j++ ) {
					VectorCopy( drawVerts[ds->firstVert+j].xyz, w->p[j] );
				}
				VectorCopy( ds->lightmapVecs[2], normal );
				if ( side ) {
					winding_t	*t;

					t = w;
					w = ReverseWinding( t );
					FreeWinding( t );
					VectorSubtract( vec3_origin, normal, normal );
				}
				SubdivideAreaLight( ls, w, normal, lightSubdivide, qtrue );
			}
		}
	}

	_printf( "%5i light emitting surfaces\n", c_lightSurfaces );
}



/*
================
FindSkyBrushes
================
*/
void FindSkyBrushes( void ) {
	int				i, j;
	dbrush_t		*b;
	skyBrush_t		*sb;
	shaderInfo_t	*si;
	dbrushside_t	*s;

	// find the brushes
	for ( i = 0 ; i < numbrushes ; i++ ) {
		b = &dbrushes[i];
		for ( j = 0 ; j < b->numSides ; j++ ) {
			s = &dbrushsides[ b->firstSide + j ];
			if ( dshaders[ s->shaderNum ].surfaceFlags & SURF_SKY ) {
				sb = &skyBrushes[ numSkyBrushes ];
				sb->b = b;
				sb->bounds[0][0] = -dplanes[ dbrushsides[ b->firstSide + 0 ].planeNum ].dist - 1;
				sb->bounds[1][0] = dplanes[ dbrushsides[ b->firstSide + 1 ].planeNum ].dist + 1;
				sb->bounds[0][1] = -dplanes[ dbrushsides[ b->firstSide + 2 ].planeNum ].dist - 1;
				sb->bounds[1][1] = dplanes[ dbrushsides[ b->firstSide + 3 ].planeNum ].dist + 1;
				sb->bounds[0][2] = -dplanes[ dbrushsides[ b->firstSide + 4 ].planeNum ].dist - 1;
				sb->bounds[1][2] = dplanes[ dbrushsides[ b->firstSide + 5 ].planeNum ].dist + 1;
				numSkyBrushes++;
				break;
			}
		}
	}

	// default
	VectorNormalize( sunDirection, sunDirection );

	// find the sky shader
	for ( i = 0 ; i < numDrawSurfaces ; i++ ) {
		si = ShaderInfoForShader( dshaders[ drawSurfaces[i].shaderNum ].shader );
		if ( si->surfaceFlags & SURF_SKY ) {
			VectorCopy( si->sunLight, sunLight );
			VectorCopy( si->sunDirection, sunDirection );
			break;
		}
	}
}

/*
=================================================================

  LIGHT SETUP

=================================================================
*/

/*
==================
FindTargetEntity
==================
*/
entity_t *FindTargetEntity( const char *target ) {
	int			i;
	const char	*n;

	for ( i = 0 ; i < num_entities ; i++ ) {
		n = ValueForKey (&entities[i], "targetname");
		if ( !strcmp (n, target) ) {
			return &entities[i];
		}
	}

	return NULL;
}



/*
=============
CreateEntityLights
=============
*/
void CreateEntityLights (void)
{
	int		i;
	light_t	*dl;
	entity_t	*e, *e2;
	const char	*name;
	const char	*target;
	vec3_t	dest;
	const char	*_color;
	float	intensity;
	int		spawnflags;
	int		numRTLights = 1;

	//
	// entities
	//
	for ( i = 0 ; i < num_entities ; i++ ) {
		e = &entities[i];
		name = ValueForKey (e, "classname");
		if (strncmp (name, "light", 5))
			continue;

		numPointLights++;
		dl = malloc(sizeof(*dl));
		memset (dl, 0, sizeof(*dl));
		dl->next = lights;
		lights = dl;

		spawnflags = FloatForKey (e, "spawnflags");
		if ( spawnflags & 1 ) {
			dl->linearLight = qtrue;
		}
		if ( spawnflags & 16 ) {
			dl->realTime = qtrue;
			dl->noGrid = qtrue;
			dl->rtLightID = numRTLights++;
			if ( spawnflags & 32 ) {
				dl->realTimeShadows = qtrue;
			}
		}

		GetVectorForKey (e, "origin", dl->origin);
		dl->style = FloatForKey (e, "_style");
		if (!dl->style)
			dl->style = FloatForKey (e, "style");
		if (dl->style < 0)
			dl->style = 0;

		intensity = FloatForKey (e, "light");
		if (!intensity)
			intensity = FloatForKey (e, "_light");
		if (!intensity)
			intensity = 300;
		_color = ValueForKey (e, "_color");
		if (_color && _color[0])
		{
			sscanf (_color, "%f %f %f", &dl->color[0],&dl->color[1],&dl->color[2]);
			ColorNormalize (dl->color, dl->color);
		}
		else
			dl->color[0] = dl->color[1] = dl->color[2] = 1.0;

		intensity = intensity * pointScale;
		dl->photons = intensity;

		dl->type = emit_point;

		// lights with a target will be spotlights
		target = ValueForKey (e, "target");

		if ( target[0] ) {
			float	radius;
			float	dist;

			e2 = FindTargetEntity (target);
			if (!e2) {
				_printf ("WARNING: light at (%i %i %i) has missing target\n",
				(int)dl->origin[0], (int)dl->origin[1], (int)dl->origin[2]);
			} else {
				GetVectorForKey (e2, "origin", dest);
				VectorSubtract (dest, dl->origin, dl->normal);
				dist = VectorNormalize (dl->normal, dl->normal);
				radius = FloatForKey (e, "radius");
				if ( !radius ) {
					radius = 64;
				}
				if ( !dist ) {
					dist = 64;
				}
				dl->radiusByDist = (radius + 16) / dist;
				dl->type = emit_spotlight;
			}
		}
	}
}

//=================================================================

/*
================
SetEntityOrigins

Find the offset values for inline models
================
*/
void SetEntityOrigins( void ) {
	int			i, j;
	entity_t	*e;
	vec3_t		origin;
	const char	*key;
	int			modelnum;
	dmodel_t	*dm;

	for ( i=0 ; i < num_entities ; i++ ) {
		e = &entities[i];
		key = ValueForKey (e, "model");
		if ( key[0] != '*' ) {
			continue;
		}
		modelnum = atoi( key + 1 );
		dm = &dmodels[ modelnum ];

		// set entity surface to true for all surfaces for this model
		for ( j = 0 ; j < dm->numSurfaces ; j++ ) {
			entitySurface[ dm->firstSurface + j ] = qtrue;
		}

		key = ValueForKey (e, "origin");
		if ( !key[0] ) {
			continue;
		}
		GetVectorForKey ( e, "origin", origin );

		// set origin for all surfaces for this model
		for ( j = 0 ; j < dm->numSurfaces ; j++ ) {
			VectorCopy( origin, surfaceOrigin[ dm->firstSurface + j ] );
		}
	}
}


/*
=================================================================


=================================================================
*/

#define	MAX_POINTS_ON_WINDINGS	64

/*
================
PointToPolygonFormFactor
================
*/
float	PointToPolygonFormFactor( const vec3_t point, const vec3_t normal, const winding_t *w, vec_t *dxDir ) {
	vec3_t		triVector, triNormal;
	vec3_t		avgPoint;
	int			i, j;
	vec3_t		dirs[MAX_POINTS_ON_WINDING];
	float		total;
	float		dot, angle, facing;
	float		area, totalArea, distSquared, dist;
	vec3_t		dir;

	for ( i = 0 ; i < w->numpoints ; i++ ) {
		VectorSubtract( w->p[i], point, dirs[i] );
		VectorNormalize( dirs[i], dirs[i] );
	}

	// duplicate first vertex to avoid mod operation
	VectorCopy( dirs[0], dirs[i] );

	VectorClear(dir);

	total = 0;
	for ( i = 0 ; i < w->numpoints ; i++ ) {
		j = i+1;
		dot = DotProduct( dirs[i], dirs[j] );

		// roundoff can cause slight creep, which gives an IND from acos
		if ( dot > 1.0 ) {
			dot = 1.0;
		} else if ( dot < -1.0 ) {
			dot = -1.0;
		}
		
		angle = acos( dot );
		CrossProduct( dirs[i], dirs[j], triVector );
		if ( VectorNormalize( triVector, triNormal ) < 0.0001 ) {
			continue;
		}

		// Accumulate direction from this triangle
		VectorAdd(dir, triVector, dir);

		facing = DotProduct( normal, triNormal );
		total += facing * angle;

		if ( total > 6.3 || total < -6.3 ) {
			static qboolean printed;

			if ( !printed ) {
				printed = qtrue;
				_printf( "WARNING: bad PointToPolygonFormFactor: %f at %1.1f %1.1f %1.1f from %1.1f %1.1f %1.1f\n", total,
					w->p[i][0], w->p[i][1], w->p[i][2], point[0], point[1], point[2]);
			}
			return 0;
		}

	}

	total /= 2*3.141592657;		// now in the range of 0 to 1 over the entire incoming hemisphere

	// Riot - Try to find the center to generate an average direction
	if(dxDir)
	{
		/*
		VectorClear(avgPoint);
		totalArea = 0.0f;
		for ( i = 2 ; i < w->numpoints ; i++ ) {
			vec3_t sideA, sideB, cross;
			vec3_t center;

			VectorSubtract(w->p[0], w->p[i-1], sideA);
			VectorSubtract(w->p[0], w->p[i], sideB);
			CrossProduct(sideA, sideB, cross);
			area = VectorLength(cross);
			totalArea += area;

			VectorCopy(w->p[0], center);
			VectorAdd(center, w->p[i-1], center);
			VectorAdd(center, w->p[i], center);

			// Contribute this center coord
			VectorMA(avgPoint, area / 3.0f, center, avgPoint);
		}
		VectorScale(avgPoint, 1.0f / totalArea, avgPoint);

		VectorSubtract(avgPoint, point, dir);
		*/

		VectorNormalize(dir, dxDir);
	}

	//_printf("Total: %f\n", total);

	return total;
}


/*
================
FilterTrace

Returns 0 to 1.0 filter fractions for the given trace
================
*/
void	FilterTrace( const vec3_t start, const vec3_t end, vec3_t filter ) {
	float		d1, d2;
	filter_t	*f;
	int			filterNum;
	vec3_t		point;
	float		frac;
	int			i;
	float		s, t;
	int			u, v;
	int			x, y;
	byte		*pixel;
	float		radius;
	float		len;
	vec3_t		total;

	filter[0] = 1.0;
	filter[1] = 1.0;
	filter[2] = 1.0;

	for ( filterNum = 0 ; filterNum < numFilters ; filterNum++ ) {
		f = &filters[ filterNum ];

		// see if the plane is crossed
		d1 = DotProduct( start, f->plane ) - f->plane[3];
		d2 = DotProduct( end, f->plane ) - f->plane[3];

		if ( ( d1 < 0 ) == ( d2 < 0 ) ) {
			continue;
		}

		// calculate the crossing point
		frac = d1 / ( d1 - d2 );

		for ( i = 0 ; i < 3 ; i++ ) {
			point[i] = start[i] + frac * ( end[i] - start[i] );
		}

		VectorSubtract( point, f->origin, point );

		s = DotProduct( point, f->vectors[0] );
		t = 1.0 - DotProduct( point, f->vectors[1] );
		if ( s < 0 || s >= 1.0 || t < 0 || t >= 1.0 ) {
			continue;
		}

		// decide the filter size
		radius = 10 * frac;
		len = VectorLength( f->vectors[0] );
		if ( !len ) {
			continue;
		}
		radius = radius * len * f->si->width;

		// look up the filter, taking multiple samples
		VectorClear( total );
		for ( u = -1 ; u <= 1 ; u++ ) {
			for ( v = -1 ; v <=1 ; v++ ) {
				x = s * f->si->width + u * radius;
				if ( x < 0 ) {
					x = 0;
				}
				if ( x >= f->si->width ) {
					x = f->si->width - 1;
				}
				y = t * f->si->height + v * radius;
				if ( y < 0 ) {
					y = 0;
				}
				if ( y >= f->si->height ) {
					y = f->si->height - 1;
				}

				pixel = f->si->pixels + ( y * f->si->width + x ) * 4;
				total[0] += pixel[0];
				total[1] += pixel[1];
				total[2] += pixel[2];
			}
		}

		filter[0] *= total[0]/(255.0*9);
		filter[1] *= total[1]/(255.0*9);
		filter[2] *= total[2]/(255.0*9);
	}

}

/*
================
SunToPoint

Returns an amount of light to add at the point
================
*/
int		c_sunHit, c_sunMiss;
void SunToPointOld( const vec3_t origin, float *occlusionDist, traceWork_t *tw, vec3_t addLight ) {
	int			i;
	trace_t		trace;
	skyBrush_t	*b;
	vec3_t		end;

	if ( !numSkyBrushes ) {
		VectorClear( addLight );
		return;
	}

	VectorMA( origin, MAX_WORLD_COORD * 2, sunDirection, end );

	TraceLine( origin, end, &trace, qtrue, qfalse, tw );

	// see if trace.hit is inside a sky brush
	for ( i = 0 ; i < numSkyBrushes ; i++) {
		b = &skyBrushes[ i ];

		// this assumes that sky brushes are axial...
		if (   trace.hit[0] < b->bounds[0][0] 
			|| trace.hit[0] > b->bounds[1][0]
			|| trace.hit[1] < b->bounds[0][1]
			|| trace.hit[1] > b->bounds[1][1]
			|| trace.hit[2] < b->bounds[0][2]
			|| trace.hit[2] > b->bounds[1][2] ) {
			continue;
		}


		// trace again to get intermediate filters
		TraceLine( origin, trace.hit, &trace, qtrue, qfalse, tw );

		// we hit the sky, so add sunlight
		if ( numthreads == 1 ) {
			c_sunHit++;
		}
		addLight[0] = trace.filter[0] * sunLight[0];
		addLight[1] = trace.filter[1] * sunLight[1];
		addLight[2] = trace.filter[2] * sunLight[2];

		return;
	}

	if ( numthreads == 1 ) {
		c_sunMiss++;
	}

	VectorClear( addLight );
}

#define SURFACE_CLIP_EPSILON 0.001
void SunToPoint( const vec3_t origin, vec3_t sunDir, traceWork_t *tw, vec3_t addLight ) {
	int			i, j, k;
	trace_t		trace;
	skyBrush_t	*b;
	vec3_t		end;
	vec3_t		nearestPoint;
	vec3_t		diff;
	qboolean	hitSkyBrush, hitAnySkyBrush;
	float		bestFraction;
	float		frac;
	float		enterFrac;

	if ( !numSkyBrushes ) {
		VectorClear( addLight );
		return;
	}

	VectorMA( origin, MAX_WORLD_COORD * 2, sunDir, end );

	bestFraction = 1.0f;

	//TraceLine( origin, end, &trace, qtrue, tw );

	hitAnySkyBrush = qfalse;

	// see if trace.hit is inside a sky brush
	for ( i = 0 ; i < numSkyBrushes ; i++) {
		dplane_t skyBrushPlanes[6];
		float f;

		b = &skyBrushes[ i ];

		origin[0];
		origin[1];
		origin[2];

		memset(skyBrushPlanes, 0, sizeof(skyBrushPlanes));
		skyBrushPlanes[0].normal[0] = 1;
		skyBrushPlanes[0].dist = b->bounds[1][0];
		skyBrushPlanes[1].normal[1] = 1;
		skyBrushPlanes[1].dist = b->bounds[1][1];
		skyBrushPlanes[2].normal[2] = 1;
		skyBrushPlanes[2].dist = b->bounds[1][2];
		skyBrushPlanes[3].normal[0] = -1;
		skyBrushPlanes[3].dist = -b->bounds[0][0];
		skyBrushPlanes[4].normal[1] = -1;
		skyBrushPlanes[4].dist = -b->bounds[0][1];
		skyBrushPlanes[5].normal[2] = -1;
		skyBrushPlanes[5].dist = -b->bounds[0][2];

		enterFrac = -1.0;
		hitSkyBrush = qtrue;
		for(j=0;j<6;j++)
		{
			float dist, d1, d2;
			dplane_t *plane = skyBrushPlanes + j;

			// adjust the plane distance apropriately for mins/maxs
			dist = plane->dist;

			d1 = DotProduct( origin, plane->normal ) - dist;
			d2 = DotProduct( end, plane->normal ) - dist;

			// if completely in front of face, no intersection with the entire brush
			if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 )  ) {
				hitSkyBrush = qfalse;
				break;
			}

			// if it doesn't cross the plane, the plane isn't relevent
			if (d1 <= 0 && d2 <= 0 ) {
				continue;
			}

			// crosses face
			if (d1 > d2) {	// enter
				f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2);
				if ( f < 0 )
					f = 0;
				if (f > enterFrac)
					enterFrac = f;
			}
		}

		if(!hitSkyBrush)
			continue;

		if(enterFrac < 0)
			continue;

		if(enterFrac < bestFraction)
			bestFraction = enterFrac;

		hitAnySkyBrush = qtrue;
	}

	if(!hitAnySkyBrush)
	{
		VectorClear( addLight );
		return;
	}

	// Get the start point of this skybox
	VectorSubtract(end, origin, diff);
	VectorMA(origin, bestFraction * 0.9999999, diff, end);

	// Shoot the trace light
	// trace again to get intermediate filters
	TraceLine( end, origin, &trace, qtrue, qfalse, tw );

	if(trace.passSolid)
	{
		VectorClear( addLight );
		return;
	}

	addLight[0] = trace.filter[0] * sunLight[0];
	addLight[1] = trace.filter[1] * sunLight[1];
	addLight[2] = trace.filter[2] * sunLight[2];
}

/*
================
SunToPlane
================
*/
void SunToPlane( const vec3_t origin, const vec3_t normal, lightcoef_t *lightCoef, traceWork_t *tw ) {
	float		angle;
	vec3_t		sunColor;
	vec3_t		resultColor, thisDir;
	vec_t		average;
	vec3_t		sunDirections[81];
	int			i, numSunDirections;
	float		scalar;

	if ( !numSkyBrushes ) {
		return;
	}

	numSunDirections = VL_CreateSunDirections(sunDirections, sunDirection, qtrue);

	scalar = 1.0f / ((float)numSunDirections);

	for(i=0;i<numSunDirections;i++)
	{
		VectorCopy(sunDirections[i], thisDir);

		angle = scalar;
		if ( angle <= 0 ) {
			return;		// facing away
		}

		SunToPoint( origin, thisDir, tw, sunColor );
		VectorScale( sunColor, angle, resultColor );
		VectorScale( resultColor, directScale, resultColor );

		ContributeLight(thisDir, lightCoef, normal, resultColor, 1.0f, 1);
	}
}

/*
================
AccumulateDirection
================
*/
void AccumulateDirection(float *currentDir, float weight, float *newDir, float *normal)
{
	float localNormal = DotProduct(normal, newDir);

	if(localNormal < 0) localNormal = 1;

	VectorMA(currentDir, weight / localNormal, newDir, currentDir);
}

/*
================
SplitLightComponents
================
*/
void SplitLightComponents(vec3_t lightDir, vec_t *averagedDir, vec3_t surfaceNormal, vec3_t baseColor, vec_t *ambient, vec_t *directed, float visibleAreaScale)
{
	vec3_t normalizedDir;
	vec3_t localDir;
	vec_t fraction;

	vec_t localAngle;

	// Add to total photon estimate
	totalPhotons += baseColor[0] + baseColor[1] + baseColor[2];
	objectivePhotons +=  baseColor[0] + baseColor[1] + baseColor[2];

	VectorNormalize(averagedDir, normalizedDir);
	VectorNormalize(lightDir, localDir);

	// Find what fraction of this light will be ambient
	// and what will be directed
	fraction = DotProduct(localDir, normalizedDir);

	if(fraction < 0.0f)
		fraction = 0.0f;

	// Add ambient contribution
	ambient[0] += baseColor[0] * (1.0f - fraction);
	ambient[1] += baseColor[1] * (1.0f - fraction);
	ambient[2] += baseColor[2] * (1.0f - fraction);

	// Figure out what the angle attenuation should be, undo it
	// since the per-pixel operations will duplicate it
	localAngle = DotProduct(normalizedDir, surfaceNormal);

	if(localAngle < 0.1f)
		return;

	directed[0] += baseColor[0] * (fraction / localAngle);
	directed[1] += baseColor[1] * (fraction / localAngle);
	directed[2] += baseColor[2] * (fraction / localAngle);
}


void LambertDirectionToSHVector(const vec3_t dir, float *out)
{
	float sqrt3 = sqrtf(3.0f);

	// Constant
	out[0] = 1.0f * SH_AMBIENT_FACTOR;

    // Linear
    out[1] = dir[1] * SH_LINEAR_FACTOR;
    out[2] = dir[2] * SH_LINEAR_FACTOR;
    out[3] = dir[0] * SH_LINEAR_FACTOR;

    // Quadratics
    out[4] = ( dir[0]*dir[1] ) * 3.0f*SH_QUADRATIC_FACTOR;
    out[5] =  ( dir[1]*dir[2] ) * 3.0f*SH_QUADRATIC_FACTOR;
    out[6] = ( 1.5f*( dir[2]*dir[2] ) - 0.5f ) * SH_QUADRATIC_FACTOR;
    out[7] =  ( dir[0]*dir[2] ) * 3.0f*SH_QUADRATIC_FACTOR;
    out[8] = 0.5f*( dir[0]*dir[0] - dir[1]*dir[1] ) * 3.0f*SH_QUADRATIC_FACTOR;

}


void ContributeLight(float *lightDir, lightcoef_t *lightCoef, float *surfaceNormal, float *color, float visibleAreaScale, int passNum)
{
#ifdef SH_ENABLED
	vec3_t normalizedDir;
	float localNormal;
	vec3_t fullContrib;
	float shVector[9];
	int i;
	float lnThreshold = 0.0001f;	// Anything below this is left in to avoid precision breakdown

	if(exactPointToPolygon)
		lnThreshold = 0.01f;		// PTPF can cause localNormal to wind up negative, so lower tolerance is needed to minimize artifacts

	if(passNum == 0)
		return;

	VectorNormalize(lightDir, normalizedDir);

	if(lightCoef->flat)
	{
		// Simple Lambert
		localNormal = DotProduct(surfaceNormal, normalizedDir);
		if(localNormal > 0.0f)
			VectorMA(lightCoef->shAmbient, localNormal, color, lightCoef->shAmbient);
	}
	else
	{
		// Spherical harmonics

		VectorScale(color, 1.0f, fullContrib);

		LambertDirectionToSHVector(normalizedDir, shVector);

		VectorMA(lightCoef->shAmbient, shVector[0], fullContrib, lightCoef->shAmbient);

		for(i=0;i<3;i++)
			VectorMA(lightCoef->shDirectional[i], shVector[1+i], fullContrib, lightCoef->shDirectional[i]);

		for(i=0;i<5;i++)
			VectorMA(lightCoef->shQuadratic[i], shVector[4+i], fullContrib, lightCoef->shQuadratic[i]);
	}


	totalPhotons += color[0] + color[1] + color[2];
	objectivePhotons +=  color[0] + color[1] + color[2];
#else
	if(passNum == 0)
		AccumulateDirection(lightCoef->direction, (color[0]+color[1]+color[2]), lightDir, surfaceNormal);
	else
		SplitLightComponents(lightDir, lightCoef->direction, surfaceNormal, color, lightCoef->ambient, lightCoef->directed, visibleAreaScale);
#endif
}

void LightCoefToLightmaps(const lightcoef_t *lightCoef, float *lightmaps)
{
#ifdef SH_ENABLED
	int i;
	for(i=0;i<3;i++)
	{
		lightmaps[i] = lightCoef->shAmbient[i];
		lightmaps[i+LIGHTMAP_BYTESIZE] = lightCoef->shDirectional[0][i];
		lightmaps[i+LIGHTMAP_BYTESIZE*2] = lightCoef->shDirectional[1][i];
		lightmaps[i+LIGHTMAP_BYTESIZE*3] = lightCoef->shDirectional[2][i];
		lightmaps[i+LIGHTMAP_BYTESIZE*4] = lightCoef->shQuadratic[0][i];
		lightmaps[i+LIGHTMAP_BYTESIZE*5] = lightCoef->shQuadratic[1][i];
		lightmaps[i+LIGHTMAP_BYTESIZE*6] = lightCoef->shQuadratic[2][i];
		lightmaps[i+LIGHTMAP_BYTESIZE*7] = lightCoef->shQuadratic[3][i];
		lightmaps[i+LIGHTMAP_BYTESIZE*8] = lightCoef->shQuadratic[4][i];
	}
#else
	int i;
	for(i=0;i<3;i++)
	{
		lightmaps[i] = lightCoef->direction[i];
		lightmaps[i+LIGHTMAP_BYTESIZE] = lightCoef->ambient[i];
		lightmaps[i+LIGHTMAP_BYTESIZE*2] = lightCoef->directed[i];
	}
#endif
}

void LightmapsToLightCoef(const float *lightmaps, lightcoef_t *lightCoef)
{
	int i;
	for(i=0;i<3;i++)
	{
#ifdef SH_ENABLED
		lightCoef->shAmbient[i] = lightmaps[i];
		lightCoef->shDirectional[0][i] = lightmaps[i+LIGHTMAP_BYTESIZE];
		lightCoef->shDirectional[1][i] = lightmaps[i+LIGHTMAP_BYTESIZE*2];
		lightCoef->shDirectional[2][i] = lightmaps[i+LIGHTMAP_BYTESIZE*3];
		lightCoef->shQuadratic[0][i] = lightmaps[i+LIGHTMAP_BYTESIZE*4];
		lightCoef->shQuadratic[1][i] = lightmaps[i+LIGHTMAP_BYTESIZE*5];
		lightCoef->shQuadratic[2][i] = lightmaps[i+LIGHTMAP_BYTESIZE*6];
		lightCoef->shQuadratic[3][i] = lightmaps[i+LIGHTMAP_BYTESIZE*7];
		lightCoef->shQuadratic[4][i] = lightmaps[i+LIGHTMAP_BYTESIZE*8];
#else
		lightCoef->direction[i] = lightmaps[i];
		lightCoef->ambient[i] = lightmaps[i+LIGHTMAP_BYTESIZE];
		lightCoef->directed[i] = lightmaps[i+LIGHTMAP_BYTESIZE*2];
#endif
	}
}



/*
================
BaseLightingAtSample
================
*/
void BaseLightingAtSample( vec3_t origin, vec3_t normal, lightcoef_t *lightCoef,
					  qboolean testOcclusion, qboolean forceSunLight, light_t *forceLight, traceWork_t *tw ) {
	light_t		*light;
	trace_t		trace;
	float		angle;
	float		add;
	float		dist;
	vec3_t		dir;
	vec3_t		occlusionDir;
	vec3_t		nDir;

	vec3_t		resultColor;
	vec_t		average;

	// trace to all the lights
	for ( light = lights ; light ; light = light->next ) {
		if(forceLight && light != forceLight)
			continue;

		//MrE: if the light is behind the surface
		if ( DotProduct(light->origin, normal) - DotProduct(normal, origin) < 0 )
			continue;
		// testing exact PTPFF
		if ( exactPointToPolygon && light->type == emit_area ) {
			float		factor;
			float		d;
			vec3_t		pushedOrigin;

			// see if the point is behind the light
			d = DotProduct( origin, light->normal ) - light->dist;
			if ( !light->twosided ) {
				if ( d < -1 ) {
					continue;		// point is behind light
				}
			}

			// test occlusion and find light filters
			// clip the line, tracing from the surface towards the light
			// Riot - Trace from light to surface
			if ( !notrace && testOcclusion ) {
				TraceLine( light->origin, origin, &trace, qfalse, qfalse, tw );

				// other light rays must not hit anything
				if ( trace.passSolid ) {
					continue;
				}
			} else {
				trace.filter[0] = 1.0;
				trace.filter[1] = 1.0;
				trace.filter[2] = 1.0;
			}

			// nudge the point so that it is clearly forward of the light
			// so that surfaces meeting a light emiter don't get black edges
			if ( d > -8 && d < 8 ) {
				VectorMA( origin, (8-d), light->normal, pushedOrigin );	
			} else {
				VectorCopy( origin, pushedOrigin );
			}

			// TODO: Undo angular attenuation if this is going to be used...

			// calculate the contribution
			factor = PointToPolygonFormFactor( pushedOrigin, normal, light->w, dir );
			if ( factor <= 0 ) {
				if ( light->twosided ) {
					factor = -factor;
				} else {
					continue;
				}
			}
			resultColor[0] = factor * light->emitColor[0] * trace.filter[0];
			resultColor[1] = factor * light->emitColor[1] * trace.filter[1];
			resultColor[2] = factor * light->emitColor[2] * trace.filter[2];

			VectorScale(resultColor, directScale, resultColor);

			VectorNormalize(dir, nDir);
			ContributeLight(nDir, lightCoef, normal, resultColor, 1.0f, 1);
			continue;
		}

		// calculate the amount of light at this sample
		if ( light->type == emit_point ) {
			VectorSubtract( light->origin, origin, dir );
			dist = VectorNormalize( dir, dir );
			// clamp the distance to prevent super hot spots
			if ( dist < 16 ) {
				dist = 16;
			}
			//angle = DotProduct( normal, dir );
			angle = 1.0f;
			if ( light->linearLight ) {
				add = angle * light->photons * linearScale - dist;
				if ( add < 0 ) {
					add = 0;
				}
			} else {
				add = light->photons / ( dist * dist ) * angle;
			}
		} else if ( light->type == emit_spotlight ) {
			float	distByNormal;
			vec3_t	pointAtDist;
			float	radiusAtDist;
			float	sampleRadius;
			vec3_t	distToSample;
			float	coneScale;

			VectorSubtract( light->origin, origin, dir );

			distByNormal = -DotProduct( dir, light->normal );
			if ( distByNormal < 0 ) {
				continue;
			}
			VectorMA( light->origin, distByNormal, light->normal, pointAtDist );
			radiusAtDist = light->radiusByDist * distByNormal;

			VectorSubtract( origin, pointAtDist, distToSample );
			sampleRadius = VectorLength( distToSample );

			if ( sampleRadius >= radiusAtDist ) {
				continue;		// outside the cone
			}
			if ( sampleRadius <= radiusAtDist - 32 ) {
				coneScale = 1.0;	// fully inside
			} else {
				coneScale = ( radiusAtDist - sampleRadius ) / 32.0;
			}
			
			dist = VectorNormalize( dir, dir );
			// clamp the distance to prevent super hot spots
			if ( dist < 16 ) {
				dist = 16;
			}
			//angle = DotProduct( normal, dir );
			angle = 1.0f;
			add = light->photons / ( dist * dist ) * angle * coneScale;

		} else if ( light->type == emit_area ) {
			VectorSubtract( light->origin, origin, dir );
			dist = VectorNormalize( dir, dir );
			// clamp the distance to prevent super hot spots
			if ( dist < 16 ) {
				dist = 16;
			}
			angle = DotProduct( normal, dir );
			if ( angle <= 0 ) {
				continue;
			}
			angle = 1.0f;
			angle *= -DotProduct( light->normal, dir );
			if ( angle <= 0 ) {
				continue;
			}

			if ( light->linearLight ) {
				add = angle * light->photons * linearScale - dist;
				if ( add < 0 ) {
					add = 0;
				}
			} else {
				add = light->photons / ( dist * dist ) * angle;
			}
		}

		if ( add <= 1.0 ) {
			continue;
		}

		// clip the line, tracing from the surface towards the light
		// Riot - Trace from light to surface
		if ( !notrace && testOcclusion ) {
			TraceLine( light->origin, origin, &trace, qtrue, qfalse, tw );

			// other light rays must not hit anything
			if ( trace.passSolid ) {
				continue;
			}
		} else {
			trace.filter[0] = 1;
			trace.filter[1] = 1;
			trace.filter[2] = 1;
		}
		
		// add the result
		resultColor[0] = add * light->color[0] * trace.filter[0];
		resultColor[1] = add * light->color[1] * trace.filter[1];
		resultColor[2] = add * light->color[2] * trace.filter[2];

		VectorScale(resultColor, directScale, resultColor);

		ContributeLight(dir, lightCoef, normal, resultColor, 1.0f, 1);
	}

	//
	// trace directly to the sun
	//
	if ( ( testOcclusion || forceSunLight ) && ( !forceLight || forceLight == &fakeSunLight ) )
		SunToPlane( origin, normal, lightCoef, tw );
}

/*
================
LightingAtSample
================
*/
void LightingAtSample(vec3_t origin, vec3_t normal, lightcoef_t *lightCoef, 
					  qboolean testOcclusion, qboolean forceSunLight, struct light_s *forceLight, traceWork_t *tw)
{
	BaseLightingAtSample(origin, normal, lightCoef, testOcclusion, forceSunLight, forceLight, tw);
}

/*
=============
PrintOccluded

For debugging
=============
*/
void PrintOccluded( byte occluded[LIGHTMAP_WIDTH*EXTRASCALE][LIGHTMAP_HEIGHT*EXTRASCALE], 
				   int width, int height ) {
	int	i, j;

	_printf( "\n" );

	for ( i = 0 ; i < height ; i++ ) {
		for ( j = 0 ; j < width ; j++ ) {
			_printf("%i", (int)occluded[j][i] );
		}
		_printf( "\n" );
	}
}


/*
=============
VertexLighting

Vertex lighting will completely ignore occlusion, because
shadows would not be resolvable anyway.
=============
*/
void VertexLighting( dsurface_t *ds, qboolean testOcclusion, qboolean forceSunLight, float scale, traceWork_t *tw ) {
	int			i, j;
	drawVert_t	*dv;
	vec3_t		direction, ambientLight, directedLight, normal;
	float		max;

	VectorCopy( ds->lightmapVecs[2], normal );

	// generate vertex lighting
	for ( i = 0 ; i < ds->numVerts ; i++ ) {
		dv = &drawVerts[ ds->firstVert + i ];

		if ( ds->patchWidth ) {
			//LightingAtSample( dv->xyz, dv->normal, direction, ambientLight, directedLight, NULL, testOcclusion, forceSunLight, qtrue, NULL, tw );
		}
		else if (ds->surfaceType == MST_TRIANGLE_SOUP) {
			//LightingAtSample( dv->xyz, dv->normal, direction, ambientLight, directedLight, NULL, testOcclusion, forceSunLight, qtrue, NULL, tw );
		}
		else {
			//LightingAtSample( dv->xyz, normal, direction, ambientLight, directedLight, NULL, testOcclusion, forceSunLight, qtrue, NULL, tw );
		}

		if (scale >= 0)
		{
			VectorScale(ambientLight, scale, ambientLight);
			VectorScale(directedLight, scale, directedLight);
		}

		// Normalize direction into 0..255
		VectorNormalize(direction, direction);

		for(j=0;j<3;j++)
		{
			if(ambientLight[j] > 255.0f)
				ambientLight[j] = 255.0f;
			if(directedLight[j] > 255.0f)
				directedLight[j] = 255.0f;

			/*
			dv->color[j] = (direction[j] * 127.5f + 127.5f);
			dv->ambientLight[j] = ambientLight[j];
			dv->directedLight[j] = directedLight[j];
			*/
		}

		// Don't bother writing alpha since it will already be set to 255,
		// plus we don't want to write over alpha generated by SetTerrainTextures
		//dv->color[3] = 255;
	}
}


/*
=================
LinearSubdivideMesh

For extra lighting, just midpoint one of the axis.
The edges are clamped at the original edges.
=================
*/
mesh_t *LinearSubdivideMesh( mesh_t *in ) {
	int			i, j;
	mesh_t		*out;
	drawVert_t	*v1, *v2, *vout;

	out = malloc( sizeof( *out ) );

	out->width = in->width * 2;
	out->height = in->height;
	out->verts = malloc( out->width * out->height * sizeof(*out->verts) );
	for ( j = 0 ; j < in->height ; j++ ) {
		out->verts[ j * out->width + 0 ] = in->verts[ j * in->width + 0 ];
		out->verts[ j * out->width + out->width - 1 ] = in->verts[ j * in->width + in->width - 1 ];
		for ( i = 1 ; i < out->width - 1 ; i+= 2 ) {
			v1 = in->verts + j * in->width + (i >> 1);
			v2 = v1 + 1;
			vout = out->verts + j * out->width + i;

			vout->xyz[0] = 0.75 * v1->xyz[0] + 0.25 * v2->xyz[0];
			vout->xyz[1] = 0.75 * v1->xyz[1] + 0.25 * v2->xyz[1];
			vout->xyz[2] = 0.75 * v1->xyz[2] + 0.25 * v2->xyz[2];

			vout->normal[0] = 0.75 * v1->normal[0] + 0.25 * v2->normal[0];
			vout->normal[1] = 0.75 * v1->normal[1] + 0.25 * v2->normal[1];
			vout->normal[2] = 0.75 * v1->normal[2] + 0.25 * v2->normal[2];

			VectorNormalize( vout->normal, vout->normal );

			vout++;

			vout->xyz[0] = 0.25 * v1->xyz[0] + 0.75 * v2->xyz[0];
			vout->xyz[1] = 0.25 * v1->xyz[1] + 0.75 * v2->xyz[1];
			vout->xyz[2] = 0.25 * v1->xyz[2] + 0.75 * v2->xyz[2];

			vout->normal[0] = 0.25 * v1->normal[0] + 0.75 * v2->normal[0];
			vout->normal[1] = 0.25 * v1->normal[1] + 0.75 * v2->normal[1];
			vout->normal[2] = 0.25 * v1->normal[2] + 0.75 * v2->normal[2];

			VectorNormalize( vout->normal, vout->normal );

		}
	}

	FreeMesh( in );

	return out;
}

/*
==============
ColorToBytes
==============
*/
void ColorToBytes( const float *color, byte *colorBytes ) {
	float	max;
	vec3_t	sample;

	VectorCopy( color, sample );

	// clamp with color normalization
	max = sample[0];
	if ( sample[1] > max ) {
		max = sample[1];
	}
	if ( sample[2] > max ) {
		max = sample[2];
	}
	if ( max > 255 ) {
		//VectorScale( sample, 255/max, sample );
	}
	if(sample[0] > 255) sample[0] = 255;
	if(sample[1] > 255) sample[1] = 255;
	if(sample[2] > 255) sample[2] = 255;
	if(sample[0] < 0) sample[0] = 0;
	if(sample[1] < 0) sample[1] = 0;
	if(sample[2] < 0) sample[2] = 0;
	colorBytes[ 0 ] = sample[0];
	colorBytes[ 1 ] = sample[1];
	colorBytes[ 2 ] = sample[2];
}

void ConvertToHDR( float *color, unsigned char *scalarByte )
{
	float	max = 0.0f;
	float	f;
	int i;
	vec3_t	sample;
	unsigned char scalar = 255;
	float fscalar;

	VectorCopy( color, sample );

	// clamp with color normalization
	for(i=0;i<3;i++)
	{
		f = fabsf(sample[i]);
		if(f > max) max = f;
	}

	if ( max > 255.0f ) {
		fscalar = (255.0f*255.0f / max);
		if(fscalar > 255.0f) fscalar = 255.0f;
		else if(fscalar < 1.0f) fscalar = 1.0f;

		// Round off
		scalar = (unsigned char)fscalar;

		fscalar = 255.0f / ((float)scalar);

		VectorScale(sample, fscalar, color);
	}

	*scalarByte = scalar;
}

void ColorToBytesHDR( const float *color, byte *colorBytes ) {
	float	max;
	vec3_t	sample;
	unsigned char scalar = 255;
	float fscalar;

	VectorCopy( color, sample );

	// clamp with color normalization
	max = sample[0];
	if ( sample[1] > max ) {
		max = sample[1];
	}
	if ( sample[2] > max ) {
		max = sample[2];
	}
	if ( max > 255 ) {
		scalar = (unsigned char)(255.0f*255.0f / max);
		if(scalar == 0) scalar = 1;
		fscalar = 255.0f / ((float)scalar);

		VectorScale(sample, fscalar, sample);
	}
	if(sample[0] > 255) sample[0] = 255;
	if(sample[1] > 255) sample[1] = 255;
	if(sample[2] > 255) sample[2] = 255;
	if(sample[0] < 0) sample[0] = 0;
	if(sample[1] < 0) sample[1] = 0;
	if(sample[2] < 0) sample[2] = 0;
	colorBytes[ 0 ] = sample[0];
	colorBytes[ 1 ] = sample[1];
	colorBytes[ 2 ] = sample[2];
	colorBytes[ 3 ] = scalar;
}



/*
=============
TraceLtm
=============
*/
typedef vec3_t vectorRow_t[LIGHTMAP_WIDTH*EXTRASCALE];
typedef byte occlusionRow_t[LIGHTMAP_WIDTH*EXTRASCALE];

void TraceLtm( int num ) {
	dsurface_t	*ds;
	int			i, j, k;
	int			x, y;
	int			position, numPositions;
	vec3_t		base, origin, normal;
	occlusionRow_t		*occluded;
	vectorRow_t		*direction, *directedLight, *ambientLight;
	traceWork_t	tw;
	vec3_t		averages[3];
	int			count;
	mesh_t		srcMesh, *mesh, *subdivided;
	shaderInfo_t	*si;
	static float	nudge[2][9] = {
		{ 0, -1, 0, 1, -1, 1, -1, 0, 1 },
		{ 0, -1, -1, -1, 0, 0, 1, 1, 1 }
	};
	int			sampleWidth, sampleHeight, ssize;
	vec3_t		lightmapOrigin, lightmapVecs[2];
	int widthtable[LIGHTMAP_WIDTH], heighttable[LIGHTMAP_WIDTH];

	ds = &drawSurfaces[num];
	si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader );

	// vertex-lit triangle model
	if ( ds->surfaceType == MST_TRIANGLE_SOUP ) {
		VertexLighting( ds, !si->noVertexShadows, si->forceSunLight, 1.0, &tw );
		return;
	}
	
	if ( ds->lightmapNum == -1 ) {
		return;		// doesn't need lighting at all
	}

	if (!novertexlighting) {
		// calculate the vertex lighting for gouraud shade mode
		VertexLighting( ds, si->vertexShadows, si->forceSunLight, si->vertexScale, &tw );
	}

	if ( ds->lightmapNum < 0 ) {
		return;		// doesn't need lightmap lighting
	}

	si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader );
	ssize = samplesize;
	if (si->lightmapSampleSize)
		ssize = si->lightmapSampleSize;

	if (si->patchShadows)
		tw.patchshadows = qtrue;
	else
		tw.patchshadows = patchshadows;

	if ( ds->surfaceType == MST_PATCH ) {
		srcMesh.width = ds->patchWidth;
		srcMesh.height = ds->patchHeight;
		srcMesh.verts = drawVerts + ds->firstVert;
		mesh = SubdivideMesh( srcMesh, 8, 999 );
		PutMeshOnCurve( *mesh );
		MakeMeshNormals( *mesh );

		subdivided = RemoveLinearMeshColumnsRows( mesh );
		FreeMesh(mesh);

		mesh = SubdivideMeshQuads( subdivided, ssize, LIGHTMAP_WIDTH, widthtable, heighttable);
		if ( mesh->width != ds->lightmapWidth || mesh->height != ds->lightmapHeight ) {
			Error( "Mesh lightmap miscount (%i != %i  ||  %i != %i)", mesh->width, ds->lightmapWidth, mesh->height, ds->lightmapHeight);
		}

		if ( extra ) {
			mesh_t	*mp;

			// chop it up for more light samples (leaking memory...)
			mp = mesh;//CopyMesh( mesh );
			mp = LinearSubdivideMesh( mp );
			mp = TransposeMesh( mp );
			mp = LinearSubdivideMesh( mp );
			mp = TransposeMesh( mp );

			mesh = mp;
		}
	} else {
		VectorCopy( ds->lightmapVecs[2], normal );

		if ( !extra ) {
			VectorCopy( ds->lightmapOrigin, lightmapOrigin );
			VectorCopy( ds->lightmapVecs[0], lightmapVecs[0] );
			VectorCopy( ds->lightmapVecs[1], lightmapVecs[1] );
		} else {
			// sample at a closer spacing for antialiasing
			VectorCopy( ds->lightmapOrigin, lightmapOrigin );
			VectorScale( ds->lightmapVecs[0], 0.5, lightmapVecs[0] );
			VectorScale( ds->lightmapVecs[1], 0.5, lightmapVecs[1] );
			VectorMA( lightmapOrigin, -0.5, lightmapVecs[0], lightmapOrigin );
			VectorMA( lightmapOrigin, -0.5, lightmapVecs[1], lightmapOrigin );
		}
	}

	if ( extra ) {
		sampleWidth = ds->lightmapWidth * 2;
		sampleHeight = ds->lightmapHeight * 2;
	} else {
		sampleWidth = ds->lightmapWidth;
		sampleHeight = ds->lightmapHeight;
	}

	// Allocate temporary storage
	occluded = malloc(sizeof(occlusionRow_t) * LIGHTMAP_HEIGHT*EXTRASCALE);
	direction = malloc(sizeof(vectorRow_t) * LIGHTMAP_HEIGHT*EXTRASCALE);
	directedLight = malloc(sizeof(vectorRow_t) * LIGHTMAP_HEIGHT*EXTRASCALE);
	ambientLight = malloc(sizeof(vectorRow_t) * LIGHTMAP_HEIGHT*EXTRASCALE);

	memset(occluded, 0, sizeof(occlusionRow_t) * LIGHTMAP_HEIGHT*EXTRASCALE);
	memset(direction, 0, sizeof(vectorRow_t) * LIGHTMAP_HEIGHT*EXTRASCALE);
	memset(directedLight, 0, sizeof(vectorRow_t) * LIGHTMAP_HEIGHT*EXTRASCALE);
	memset(ambientLight, 0, sizeof(vectorRow_t) * LIGHTMAP_HEIGHT*EXTRASCALE);

	// determine which samples are occluded
	for ( i = 0 ; i < sampleWidth ; i++ ) {
		for ( j = 0 ; j < sampleHeight ; j++ ) {

			if ( ds->patchWidth ) {
				numPositions = 9;
				VectorCopy( mesh->verts[j*mesh->width+i].normal, normal );
				// VectorNormalize( normal, normal );
				// push off of the curve a bit
				VectorMA( mesh->verts[j*mesh->width+i].xyz, 1, normal, base );

				MakeNormalVectors( normal, lightmapVecs[0], lightmapVecs[1] );
			} else {
				numPositions = 9;
				for ( k = 0 ; k < 3 ; k++ ) {
					base[k] = lightmapOrigin[k] + normal[k]
						+ i * lightmapVecs[0][k] 
						+ j * lightmapVecs[1][k];
				}
			}
			VectorAdd( base, surfaceOrigin[ num ], base );

			// we may need to slightly nudge the sample point
			// if directly on a wall
			for ( position = 0 ; position < numPositions ; position++ ) {
				// calculate lightmap sample position
				for ( k = 0 ; k < 3 ; k++ ) {
					origin[k] = base[k] + 
						+ ( nudge[0][position]/16 ) * lightmapVecs[0][k] 
						+ ( nudge[1][position]/16 ) * lightmapVecs[1][k];
				}

				if ( notrace ) {
					break;
				}
				if ( !PointInSolid( origin, qfalse ) ) {
					break;
				}
			}

			// if none of the nudges worked, this sample is occluded
			if ( position == numPositions ) {
				occluded[i][j] = qtrue;
				if ( numthreads == 1 ) {
					c_occluded++;
				}
				continue;
			}
			
			if ( numthreads == 1 ) {
				c_visible++;
			}
			occluded[i][j] = qfalse;
			//LightingAtSample( origin, normal, direction[i][j], ambientLight[i][j], directedLight[i][j], NULL, qtrue, qfalse, qtrue, NULL, &tw );
		}
	}

	if ( dump ) {
		PrintOccluded( occluded, sampleWidth, sampleHeight );
	}

	// calculate average values for occluded samples
	for ( i = 0 ; i < sampleWidth ; i++ ) {
		for ( j = 0 ; j < sampleHeight ; j++ ) {
			if ( !occluded[i][j] ) {
				continue;
			}
			// scan all surrounding samples
			count = 0;
			VectorClear( averages[0] );
			VectorClear( averages[1] );
			VectorClear( averages[2] );
			for ( x = -1 ; x <= 1; x++ ) {
				for ( y = -1 ; y <= 1 ; y++ ) {
					if ( i + x < 0 || i + x >= sampleWidth ) {
						continue;
					}
					if ( j + y < 0 || j + y >= sampleHeight ) {
						continue;
					}
					if ( occluded[i+x][j+y] ) {
						continue;
					}
					count++;
					VectorAdd( direction[i+x][j+y], averages[0], averages[0] );
					VectorAdd( ambientLight[i+x][j+y], averages[1], averages[1] );
					VectorAdd( directedLight[i+x][j+y], averages[2], averages[2] );
				}
			}
			if ( count ) {
				VectorScale( averages[0], 1.0/count, direction[i][j] );
				VectorScale( averages[1], 1.0/count, ambientLight[i][j] );
				VectorScale( averages[2], 1.0/count, directedLight[i][j] );
			}
		}
	}

	// average together the values if we are extra sampling
	if ( ds->lightmapWidth != sampleWidth ) {
		for ( i = 0 ; i < ds->lightmapWidth ; i++ ) {
			for ( j = 0 ; j < ds->lightmapHeight ; j++ ) {
				for ( k = 0 ; k < 3 ; k++ ) {
					float		value, coverage;

					value = direction[i*2][j*2][k] + direction[i*2][j*2+1][k] +
						direction[i*2+1][j*2][k] + direction[i*2+1][j*2+1][k];
					direction[i][j][k] = value * 0.25f;

					value = directedLight[i*2][j*2][k] + directedLight[i*2][j*2+1][k] +
						directedLight[i*2+1][j*2][k] + directedLight[i*2+1][j*2+1][k];
					directedLight[i][j][k] = value * 0.25f;

					value = ambientLight[i*2][j*2][k] + ambientLight[i*2][j*2+1][k] +
						ambientLight[i*2+1][j*2][k] + ambientLight[i*2+1][j*2+1][k];
					ambientLight[i][j][k] = value * 0.25f;
				}
			}
		}
	}

	// optionally create a debugging border around the lightmap
	if ( lightmapBorder ) {
		for ( i = 0 ; i < ds->lightmapWidth ; i++ ) {
			ambientLight[i][0][0] = 255;
			ambientLight[i][0][1] = 0;
			ambientLight[i][0][2] = 0;

			ambientLight[i][ds->lightmapHeight-1][0] = 255;
			ambientLight[i][ds->lightmapHeight-1][1] = 0;
			ambientLight[i][ds->lightmapHeight-1][2] = 0;
		}
		for ( i = 0 ; i < ds->lightmapHeight ; i++ ) {
			ambientLight[0][i][0] = 255;
			ambientLight[0][i][1] = 0;
			ambientLight[0][i][2] = 0;

			ambientLight[ds->lightmapWidth-1][i][0] = 255;
			ambientLight[ds->lightmapWidth-1][i][1] = 0;
			ambientLight[ds->lightmapWidth-1][i][2] = 0;
		}
	}

	// clamp the colors to bytes and store off
	for ( i = 0 ; i < ds->lightmapWidth ; i++ ) {
		for ( j = 0 ; j < ds->lightmapHeight ; j++ ) {
			k = ( ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + j) 
				* LIGHTMAP_WIDTH + ds->lightmapX + i;

			VectorCopy( direction[i][j], (lightFloats + k*3) );
			VectorCopy( ambientLight[i][j], (lightFloats + k*3 + LIGHTMAP_BYTESIZE) );
			VectorCopy( directedLight[i][j], (lightFloats + k*3 + LIGHTMAP_BYTESIZE*2) );
		}
	}

	if (ds->surfaceType == MST_PATCH)
	{
		FreeMesh(mesh);
	}

	// Free temporary arrays
	free(occluded);
	free(direction);
	free(directedLight);
	free(ambientLight);
}


//=============================================================================

vec3_t	gridMins;
vec3_t	gridSize = { 32, 32, 64 };
int		gridBounds[3];


/*
========================
LightContributionToPoint
========================
*/
qboolean LightContributionToPoint( const light_t *light, const vec3_t origin,
								  vec3_t color, traceWork_t *tw ) {
	trace_t		trace;
	float		add;

	add = 0;

	VectorClear( color );

	// testing exact PTPFF
	if ( exactPointToPolygon && light->type == emit_area ) {
		float		factor;
		float		d;
		vec3_t		normal;

		// see if the point is behind the light
		d = DotProduct( origin, light->normal ) - light->dist;
		if ( !light->twosided ) {
			if ( d < 1 ) {
				return qfalse;		// point is behind light
			}
		}

		// test occlusion
		// clip the line, tracing from the surface towards the light
		TraceLine( origin, light->origin, &trace, qfalse, qfalse, tw );
		if ( trace.passSolid ) {
			return qfalse;
		}

		// calculate the contribution
		VectorSubtract( light->origin, origin, normal );
		if ( VectorNormalize( normal, normal ) == 0 ) {
			return qfalse;
		}
		factor = PointToPolygonFormFactor( origin, normal, light->w, NULL );
		if ( factor <= 0 ) {
			if ( light->twosided ) {
				factor = -factor;
			} else {
				return qfalse;
			}
		}
		VectorScale( light->emitColor, factor, color );
		return qtrue;
	}

	// calculate the amount of light at this sample
	if ( light->type == emit_point || light->type == emit_spotlight ) {
		vec3_t		dir;
		float		dist;

		VectorSubtract( light->origin, origin, dir );
		dist = VectorLength( dir );
		// clamp the distance to prevent super hot spots
		if ( dist < 16 ) {
			dist = 16;
		}
		if ( light->linearLight ) {
			add = light->photons * linearScale - dist;
			if ( add < 0 ) {
				add = 0;
			}
		} else {
			add = light->photons / ( dist * dist );
		}
	} else {
		return qfalse;
	}

	if ( add <= 1.0 ) {
		return qfalse;
	}

	// clip the line, tracing from the surface towards the light
	TraceLine( origin, light->origin, &trace, qfalse, qfalse, tw );

	// other light rays must not hit anything
	if ( trace.passSolid ) {
		return qfalse;
	}

	// add the result
	color[0] = add * light->color[0];
	color[1] = add * light->color[1];
	color[2] = add * light->color[2];

	return qtrue;
}

#define MAX_CONTRIBUTIONS	4

typedef struct {
	vec3_t		dir;
	vec3_t		color;
	float		intensity;
	int			rtLightID;
} contribution_t;

void DischargeContribution(const contribution_t *cont, vec3_t *coefs)
{
	float factors[9];
	int i;

	LambertDirectionToSHVector(cont->dir, factors);

	for(i=0;i<9;i++)
	{
		VectorMA(coefs[i], factors[i], cont->color, coefs[i]);
	}
}

/*
=============
TraceGrid

Grid samples are foe quickly determining the lighting
of dynamically placed entities in the world
=============
*/
void TraceGrid( int num ) {
	int			x, y, z;
	vec3_t		origin;
	light_t		*light;
	vec3_t		color;
	int			mod;
	vec3_t		directedColor;
	vec3_t		coefs[9];
	float		highest;
	float		maxMultiplier = 32.0f;

	dgridpoint_t	gridPoint;
	contribution_t	contrib;
	contribution_t	contribs[MAX_CONTRIBUTIONS];
	int			numCon;
	int			i, j;
	traceWork_t	tw;
	float		addSize;

	mod = num;
	z = mod / ( gridBounds[0] * gridBounds[1] );
	mod -= z * ( gridBounds[0] * gridBounds[1] );

	y = mod / gridBounds[0];
	mod -= y * gridBounds[0];

	x = mod;

	origin[0] = gridMins[0] + x * gridSize[0];
	origin[1] = gridMins[1] + y * gridSize[1];
	origin[2] = gridMins[2] + z * gridSize[2];

	if ( PointInSolid( origin, qtrue ) ) {
		vec3_t	baseOrigin;
		int		step;

		VectorCopy( origin, baseOrigin );

		// try to nudge the origin around to find a valid point
		for ( step = 9 ; step <= 18 ; step += 9 ) {
			for ( i = 0 ; i < 8 ; i++ ) {
				VectorCopy( baseOrigin, origin );
				if ( i & 1 ) {
					origin[0] += step;
				} else {
					origin[0] -= step;
				}
				if ( i & 2 ) {
					origin[1] += step;
				} else {
					origin[1] -= step;
				}
				if ( i & 4 ) {
					origin[2] += step;
				} else {
					origin[2] -= step;
				}

				if ( !PointInSolid( origin, qtrue ) ) {
					break;
				}
			}
			if ( i != 8 ) {
				break;
			}
		}
		if ( step > 18 ) {
			// can't find a valid point at all
			return;
		}
	}

	for(i=0;i<9;i++)
	{
		VectorClear(coefs[i]);
	}

	// trace to all the lights

	// find the major light direction, and divide the
	// total light between that along the direction and
	// the remaining in the ambient 
	numCon = 0;

	for ( light = lights ; light ; light = light->next ) {
		vec3_t		add;
		vec3_t		dir;
		float		addSize;

		if ( !LightContributionToPoint( light, origin, add, &tw ) ) {
			continue;
		}

		VectorSubtract( light->origin, origin, dir );
		VectorNormalize( dir, dir );

		VectorCopy( add, contrib.color );
		VectorCopy( dir, contrib.dir );
		numCon++;

		DischargeContribution(&contrib, coefs);
	}

	//
	// trace directly to the sun
	//
	SunToPoint( origin, sunDirection, &tw, color );
	addSize = VectorLength( color );
	if ( addSize > 0 ) {
		VectorCopy( color, contrib.color );
		VectorCopy( sunDirection, contrib.dir );
		DischargeContribution(&contrib, coefs);
		numCon++;
	}


	// Add radiosity
	if(gridradiosity)
	{
		for(i=0;i<9;i++)
			VectorMA(coefs[i], radiosityScale, gridradiosity[num*9 + i], coefs[i]);
	}

	//
	// save the resulting value out
	//
	highest = 0.00001f;
	for(i=0;i<9;i++)
	{
		for(j=0;j<3;j++)
		{
			float absv = fabsf(coefs[i][j]);
			if(highest < absv) highest = absv;
		}
	}

	if(highest < 255.0f)
		highest = 255.0f;

	for(i=0;i<9;i++)
	{
		for(j=0;j<3;j++)
		{
			float v = coefs[i][j] / highest;
			if(v < 0.0f) v = -sqrtf(-v); else v = sqrtf(v);
			if(i)
				v = v * 127.5f + 127.5f;
			else
				v *= 255.0f;
			if(v < 0.0f) v = 0.0f;
			else if(v > 255.0f) v = 255.0f;

			gridPoint.coefs[i][j] = (unsigned char)v;
		}
	}

	highest = (highest - 255.0f) / maxMultiplier ;
	if(highest > 255.0f) highest = 255.0f;
	gridPoint.mult = (unsigned char)highest;

	gridData[num] = gridPoint;
}


/*
=============
SetupGrid
=============
*/
void SetupGrid( void ) {
	int		i;
	vec3_t	maxs;

	for ( i = 0 ; i < 3 ; i++ ) {
		gridMins[i] = gridSize[i] * ceil( dmodels[0].mins[i] / gridSize[i] );
		maxs[i] = gridSize[i] * floor( dmodels[0].maxs[i] / gridSize[i] );
		gridBounds[i] = (maxs[i] - gridMins[i])/gridSize[i] + 1;
		dmodels[0].gridBounds[i] = gridBounds[i];
		dmodels[0].gridScalar = 1.0f;
	}

	VectorCopy(gridSize, dmodels[0].gridSize);
	VectorCopy(gridMins, dmodels[0].gridOrigin);

	numGridPoints = gridBounds[0] * gridBounds[1] * gridBounds[2];
	if (numGridPoints >= MAX_MAP_LIGHTGRID)
		Error("MAX_MAP_LIGHTGRID");
	qprintf( "%5i gridPoints\n", numGridPoints );
}

//=============================================================================

/*
=============
RemoveLightsInSolid
=============
*/
void RemoveLightsInSolid(void)
{
	light_t *light, *prev;
	int numsolid = 0;

	prev = NULL;
	for ( light = lights ; light ;  ) {
		if (PointInSolid(light->origin, qfalse))
		{
			if (prev) prev->next = light->next;
			else lights = light->next;
			if (light->w)
				FreeWinding(light->w);
			free(light);
			numsolid++;
			if (prev)
				light = prev->next;
			else
				light = lights;
		}
		else
		{
			prev = light;
			light = light->next;
		}
	}
	_printf (" %7i lights in solid\n", numsolid);
}

/*
=============
LightWorld
=============
*/
void LightWorld (void) {
	float		f;

	// determine the number of grid points

	// riot - Disabled
	numGridPoints = 0;
	//SetupGrid();

	// find the optional world ambient
	GetVectorForKey( &entities[0], "_color", ambientColor );
	f = FloatForKey( &entities[0], "ambient" );
	VectorScale( ambientColor, f, ambientColor );

	// create lights out of patches and lights
	qprintf ("--- CreateLights ---\n");
	CreateEntityLights ();
	qprintf ("%i point lights\n", numPointLights);
	qprintf ("%i area lights\n", numAreaLights);

	if (!nogridlighting) {
		qprintf ("--- TraceGrid ---\n");
		RunThreadsOnIndividual( numGridPoints, qtrue, TraceGrid );
		qprintf( "%i x %i x %i = %i grid\n", gridBounds[0], gridBounds[1],
			gridBounds[2], numGridPoints);
	}

	qprintf ("--- TraceLtm ---\n");
	RunThreadsOnIndividual( numDrawSurfaces, qtrue, TraceLtm );
	qprintf( "%5i visible samples\n", c_visible );
	qprintf( "%5i occluded samples\n", c_occluded );
}

/*
========
CreateFilters

EXPERIMENTAL, UNUSED

Look for transparent light filter surfaces.

This will only work for flat 3*3 patches that exactly hold one copy of the texture.
========
*/
#define	PLANAR_PATCH_EPSILON	0.1
void CreateFilters( void ) {
	int				i;
	filter_t		*f;
	dsurface_t		*ds;
	shaderInfo_t	*si;
	drawVert_t		*v1, *v2, *v3;
	vec3_t			d1, d2;
	int				vertNum;

	numFilters = 0;

	return;

	for ( i = 0 ; i < numDrawSurfaces ; i++ ) {
		ds = &drawSurfaces[i];
		if ( !ds->patchWidth ) {
			continue;
		}
		si = ShaderInfoForShader( dshaders[ ds->shaderNum ].shader );
/*
		if ( !(si->surfaceFlags & SURF_LIGHTFILTER) ) {
			continue;
		}
*/

		// we have a filter patch
		v1 = &drawVerts[ ds->firstVert ];

		if ( ds->patchWidth != 3 || ds->patchHeight != 3 ) {
			_printf("WARNING: patch at %i %i %i has SURF_LIGHTFILTER but isn't a 3 by 3\n",
				v1->xyz[0], v1->xyz[1], v1->xyz[2] );
			continue;
		}

		if ( numFilters == MAX_FILTERS ) {
			Error( "MAX_FILTERS" );
		}
		f = &filters[ numFilters ];
		numFilters++;

		v2 = &drawVerts[ ds->firstVert + 2 ];
		v3 = &drawVerts[ ds->firstVert + 6 ];

		VectorSubtract( v2->xyz, v1->xyz, d1 );
		VectorSubtract( v3->xyz, v1->xyz, d2 );
		VectorNormalize( d1, d1 );
		VectorNormalize( d2, d2 );
		CrossProduct( d1, d2, f->plane );
		f->plane[3] = DotProduct( v1->xyz, f->plane );

		// make sure all the control points are on the plane
		for ( vertNum = 0 ; vertNum < ds->numVerts ; vertNum++ ) {
			float	d;

			d = DotProduct( drawVerts[ ds->firstVert + vertNum ].xyz, f->plane ) - f->plane[3];
			if ( fabs( d ) > PLANAR_PATCH_EPSILON ) {
				break;
			}
		}
		if ( vertNum != ds->numVerts ) {
			numFilters--;
			_printf("WARNING: patch at %i %i %i has SURF_LIGHTFILTER but isn't flat\n",
				v1->xyz[0], v1->xyz[1], v1->xyz[2] );
			continue;
		}
	}

	f = &filters[0];
	numFilters = 1;

	f->plane[0] = 1;
	f->plane[1] = 0;
	f->plane[2] = 0;
	f->plane[3] = 448;

	f->origin[0] = 448;
	f->origin[1] = 192;
	f->origin[2] = 0;

	f->vectors[0][0] = 0;
	f->vectors[0][1] = -1.0 / 128;
	f->vectors[0][2] = 0;

	f->vectors[1][0] = 0;
	f->vectors[1][1] = 0;
	f->vectors[1][2] = 1.0 / 128;

	f->si = ShaderInfoForShader( "textures/hell/blocks11ct" );
}

/*
=============
VertexLightingThread
=============
*/
void VertexLightingThread(int num) {
	dsurface_t	*ds;
	traceWork_t	tw;
	shaderInfo_t *si;

	ds = &drawSurfaces[num];

	// vertex-lit triangle model
	if ( ds->surfaceType == MST_TRIANGLE_SOUP ) {
		return;
	}

	if (novertexlighting)
		return;

	if ( ds->lightmapNum == -1 ) {
		return;	// doesn't need lighting at all
	}

	si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader );

	// calculate the vertex lighting for gouraud shade mode
	VertexLighting( ds, si->vertexShadows, si->forceSunLight, si->vertexScale, &tw );
}

/*
=============
TriSoupLightingThread
=============
*/
void TriSoupLightingThread(int num) {
	dsurface_t	*ds;
	traceWork_t	tw;
	shaderInfo_t *si;

	ds = &drawSurfaces[num];
	si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader );

	// vertex-lit triangle model
	if ( ds->surfaceType == MST_TRIANGLE_SOUP ) {
		VertexLighting( ds, !si->noVertexShadows, si->forceSunLight, 1.0, &tw );
	}
}

/*
=============
GridAndVertexLighting
=============
*/
void GridAndVertexLighting(void) {
	SetupGrid();

	FindSkyBrushes();
	CreateFilters();
	InitTrace();
	CreateEntityLights ();
	CreateSurfaceLights();

	CalcGridRadiosity();

	if (!nogridlighting) {
		_printf ("--- TraceGrid ---\n");
		Progress_SetMessage("Calculating grid lighting...");
		RunThreadsOnIndividual( numGridPoints, qtrue, TraceGrid );
	}

	if(gridradiosity)
		free(gridradiosity);

	if (!novertexlighting) {
		_printf ("--- Vertex Lighting ---\n");
		//RunThreadsOnIndividual( numDrawSurfaces, qtrue, VertexLightingThread );
	}

	_printf("--- Model Lighting ---\n");
	//RunThreadsOnIndividual( numDrawSurfaces, qtrue, TriSoupLightingThread );
}

/*
========
LightMain

========
*/
int LightMain (int argc, char **argv) {
	int			i;
	double		start, end;
	const char	*value;

	_printf ("----- Lighting ----\n");

	verbose = qfalse;

	for (i=1 ; i<argc ; i++) {
		if (!strcmp(argv[i],"-tempname"))
    {
      i++;
    } else if (!strcmp(argv[i],"-v")) {
			verbose = qtrue;
		} else if (!strcmp(argv[i],"-threads")) {
			numthreads = atoi (argv[i+1]);
			i++;
		} else if (!strcmp(argv[i],"-area")) {
			areaScale *= atof(argv[i+1]);
			_printf ("area light scaling at %f\n", areaScale);
			i++;
		} else if (!strcmp(argv[i],"-point")) {
			pointScale *= atof(argv[i+1]);
			_printf ("point light scaling at %f\n", pointScale);
			i++;
		} else if (!strcmp(argv[i],"-notrace")) {
			notrace = qtrue;
			_printf ("No occlusion tracing\n");
		} else if (!strcmp(argv[i],"-patchshadows")) {
			patchshadows = qtrue;
			_printf ("Patch shadow casting enabled\n");
		} else if (!strcmp(argv[i],"-extra")) {
			extra = qtrue;
			_printf ("Extra detail tracing\n");
		} else if (!strcmp(argv[i],"-extrawide")) {
			extra = qtrue;
			extraWide = qtrue;
			_printf ("Extra wide detail tracing\n");
		} else if (!strcmp(argv[i], "-samplesize")) {
			samplesize = atoi(argv[i+1]);
			if (samplesize < 1) samplesize = 1;
			i++;
			_printf("lightmap sample size is %dx%d units\n", samplesize, samplesize);
		} else if (!strcmp(argv[i], "-novertex")) {
			novertexlighting = qtrue;
			_printf("no vertex lighting = true\n");
		} else if (!strcmp(argv[i], "-nogrid")) {
			nogridlighting = qtrue;
			_printf("no grid lighting = true\n");
		} else if (!strcmp(argv[i], "-nogridrad")) {
			nogridradiosity = qtrue;
			_printf("no grid radiosity = true\n");
		} else if (!strcmp(argv[i],"-border")) {
			lightmapBorder = qtrue;
			_printf ("Adding debug border to lightmaps\n");
		} else if (!strcmp(argv[i],"-nosurf")) {
			noSurfaces = qtrue;
			_printf ("Not tracing against surfaces\n" );
		} else if (!strcmp(argv[i],"-dump")) {
			dump = qtrue;
			_printf ("Dumping occlusion maps\n");
		} else {
			break;
		}
	}

	ThreadSetDefault ();

	if (i != argc - 1) {
		_printf("usage: q3map -light [-<switch> [-<switch> ...]] <mapname>\n"
				"\n"
				"Switches:\n"
				"   v              = verbose output\n"
				"   threads <X>    = set number of threads to X\n"
				"   area <V>       = set the area light scale to V\n"
				"   point <W>      = set the point light scale to W\n"
				"   notrace        = don't cast any shadows\n"
				"   extra          = enable super sampling for anti-aliasing\n"
				"   extrawide      = same as extra but smoothen more\n"
				"   nogrid         = don't calculate light grid for dynamic model lighting\n"
				"   novertex       = don't calculate vertex lighting\n"
				"   samplesize <N> = set the lightmap pixel size to NxN units\n");
		exit(0);
	}

	start = I_FloatTime ();

	SetQdirFromPath (argv[i]);	

#ifdef _WIN32
	InitPakFile(gamedir, NULL);
#endif

	strcpy (source, ExpandArg(argv[i]));
	StripExtension (source);
	DefaultExtension (source, ".bsp");

	LoadShaderInfo();

	_printf ("reading %s\n", source);

	LoadBSPFile (source);

	FindSkyBrushes();

	ParseEntities();

	value = ValueForKey( &entities[0], "gridsize" );
	if (strlen(value)) {
		sscanf( value, "%f %f %f", &gridSize[0], &gridSize[1], &gridSize[2] );
		_printf("grid size = {%1.1f, %1.1f, %1.1f}\n", gridSize[0], gridSize[1], gridSize[2]);
	}

	CreateFilters();

	InitTrace();

	SetEntityOrigins();

	CountLightmaps();

	CreateSurfaceLights();

	LightWorld();

	_printf ("writing %s\n", source);
	WriteBSPFile (source);

	end = I_FloatTime ();
	_printf ("%5.0f seconds elapsed\n", end-start);

	/*
	for(i=0;i<numLightBytes;i+= LIGHTMAP_BYTESIZE)
	{
		char filename[1024];
		sprintf(filename, "lightmaptest%4i.tga", i /LIGHTMAP_BYTESIZE);
		WriteTGA(filename, lightBytes + i, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, 3);
	}
	*/

	return 0;
}

